package com.mongo.data.utils;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.FatalBeanException;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author yangxiaoming@cmss.chinamobile.com
 * @version Jun 10, 2019 3:36:26 PM
 */
@Slf4j
public class BeanUtil {

    /**
     * Generate targetClass instance valued of source object
     */
    public static <T> List<T> generateTarget(List<?> sources, Class<T> targetClass) {
        Assert.notNull(sources, "Source list object not allowed with null");
        List<T> targets = new ArrayList<>(sources.size());
        for (Object source : sources) {
            T t = generateTarget(source, targetClass);
            targets.add(t);
        }
        return targets;
    }

    /**
     * Generate targetClass instance valued of source object
     */
    @SuppressWarnings("unchecked")
    public static <T> T generateTarget(Object source, Class<T> targetClass) {
        if (source == null) {
            return null;
        }
        T t = null;
        try {
            //先实例化
            t = (T) instance4Class(source, targetClass);
            //后做深拷贝
            t = deepCopyProcess(source, t);
        } catch (Exception e) {
            log.warn("暂无处理", e);
        }
        return t;
    }

    /**
     * Generate targetClass instance valued of source object
     */
    public static <T> Page<T> generateTarget(Page<?> sources, Class<T> targetClass) {
        Assert.notNull(sources, "Source object not allowed with null");
        List<?> sourceList = sources.getRecords();
        List<T> targetList = generateTarget(sourceList, targetClass);
        Page<T> pages = new Page<>();
        BeanUtils.copyProperties(sources, pages);
        pages.setRecords(targetList);
        return pages;
    }

    /**
     * Generate targetClass instance valued of source object
     */
    public static <T> Set<T> generateTargetToSet(List<?> sources, Class<T> targetClass) {
        Assert.notNull(sources, "Source list object not allowed with null");
        Set<T> targets = new HashSet<>(sources.size());
        for (Object source : sources) {
            T t = generateTarget(source, targetClass);
            targets.add(t);
        }
        return targets;
    }

    /**
     * 深拷贝处理
     * <p>
     * 用于generateTarget()，生成一个新的对象； 用于copyIgnoreNullField()，拷贝属性
     */
    private static <T> T deepCopyProcess(Object source, T t)
            throws IllegalAccessException, ClassNotFoundException {
        Class<?> targetClass = t.getClass();
        //不需要深拷贝的类型
        if (Enum.class.isAssignableFrom(targetClass) || targetClass == String.class || isBasicClass(
                targetClass)) {
            return t;
        }
        // Get all fields of target class
        Map<String, Field> targetFields = getAllFields(targetClass);
        // Get all fields of source class
        Map<String, Field> sourceFileds = getAllFields(source.getClass());

        Set<Entry<String, Field>> targetEntry = targetFields.entrySet();
        if (targetEntry.size() == 0) {
            //适用于一般类型或者简单类型等
            if (targetClass == String[].class) {
                String[] strs = (String[]) source;
                String[] ts = (String[]) t;
                System.arraycopy(strs, 0, ts, 0, strs.length);
            } else {
                t = (T) source;
            }
        }

        for (Entry<String, Field> fieldEntry : targetEntry) {
            // Get target field
            Field targetField = fieldEntry.getValue();
            String key = fieldEntry.getKey();

            // Get source filed
            Field sourceField = sourceFileds.get(key);
            if (sourceField == null) {
                // Skip execute setter for target field
                continue;
            }

            // Set source field accessible
            boolean sourceAccessible = sourceField.isAccessible();
            sourceField.setAccessible(true);

            // Set target field accessible
            boolean targetAccess = targetField.isAccessible();
            targetField.setAccessible(true);

            Object obj = sourceField.get(source);
            if (obj == null) {
                continue;
            }
            try {
                Class<?> targetFieldClass = targetField.getType();
                if (isListField(targetField)) {
                    List<?> sourceList = (List<?>) obj;
                    List<String> classList = new ArrayList<>();
                    String generic = targetField.getGenericType().getTypeName();

                    String actualClass;
                    while (generic.contains("<")) {
                        actualClass = generic.substring(0, generic.indexOf("<"));
                        classList.add(actualClass);
                        generic = generic.substring(generic.indexOf("<") + 1, generic.length() - 1);
                    }
                    classList.add(generic);
                    int size = classList.size();
                    if (size > 2) {
                        log.warn("暂无处理");
                    } else {
                        targetFieldClass = Class.forName(generic);
                        targetField.set(t, generateTarget(sourceList, targetFieldClass));
                    }
                } else if (Map.class.isAssignableFrom(targetField.getType())) {
                    if (obj instanceof Map) {
                        //默认设置为HaspMap且对map中的内容不深拷贝
                        targetField.set(t, Maps.newHashMap((Map<?, ?>) obj));
                    } else {
                        log.warn("source 不是Map类型无法复制");
                    }
                } else if (Set.class.isAssignableFrom(targetField.getType())) {
                    if (obj instanceof Set) {
                        //默认设置为HaspSet且对set中的内容不深拷贝
                        targetField.set(t, Sets.newHashSet((Set<?>) obj));
                    } else {
                        log.warn("source 不是Set类型无法复制");
                    }
                } else if (Date.class.isAssignableFrom(targetField.getType())) {
                    //判断targetClass是否可由java.util.Date及其子类的对象赋值
                    if (obj instanceof Date) {
                        Date sourceDate = (Date) obj;
                        targetField.set(t, Date.from(sourceDate.toInstant()));
                    } else {
                        //类型不匹配
                        log.debug("source 不是Date类型的无法复制");
                    }
                } else if (targetField.getType().isInterface()) {
                    log.warn("target Field 类型为未识别的接口类型{}，暂无处理", targetField.getType().getName());
                } else {
                    Object targetFieldValue = targetField.get(t);
                    if (targetFieldValue == null || Enum.class.isAssignableFrom(targetFieldClass)
                            || targetFieldClass == String.class || isBasicClass(targetFieldClass)) {

                        //当该字段目标是null、枚举类、String或者基本类型，则生成一个新的obj
                        targetField.set(t, generateTarget(obj, targetField.getType()));
                    } else {
                        //当该字段目标不是null，则拷贝
                        copyIgnoreNullField(obj, targetFieldValue);
                        targetField.set(t, targetFieldValue);
                    }
                }

                // Reset source field access
                sourceField.setAccessible(sourceAccessible);
                // Reset target field access
                targetField.setAccessible(targetAccess);
            } catch (Exception e) {
                log.warn("字段更新失败，原因： {}", e.getMessage());
            }
        }
        return t;
    }

    /**
     * Copy source value to target
     */
    public static <T> T copy(Object source, T target) {
        Assert.notNull(source, "Source object not allowed with null");
        Assert.notNull(target, "target object not allowed with null");
        BeanUtils.copyProperties(source, target);
        return target;
    }

    /**
     * Acquire all fields of specific class (includes super class)
     */
    private static Map<String, Field> getAllFields(Class<?> clazz) {
        Map<String, Field> fieldMap = new HashMap<>();
        List<Field> fieldList = new ArrayList<>();
        while (clazz != null) {
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                if (!isOmit(field, fieldList)) {
                    fieldList.add(field);
                    fieldMap.put(getFieldName(field), field);
                }
            }
            clazz = clazz.getSuperclass();
        }
        return fieldMap;
    }

    /**
     * new instance for specific class
     */
    private static <T> Object instance4Class(Object source, Class<T> clazz)
            throws InstantiationException, IllegalAccessException {
        if (clazz.isInterface()) {
            if (clazz == List.class) {
                return new ArrayList<T>();
            } else if (clazz == Map.class) {
                return new HashMap<>();
            } else {
                throw new InstantiationException("Can not instantize interface: " + clazz);
            }
        }
        if (clazz == String[].class) {
            return new String[((String[]) source).length];
        }
        if (clazz == Integer.class) {
            return Integer.parseInt(source.toString());
        } else if (clazz == Character.class) {
            return source.toString().charAt(0);
        } else if (clazz == Byte.class) {
            return Byte.parseByte(source.toString());
        } else if (clazz == Double.class) {
            return Double.parseDouble(source.toString());
        } else if (clazz == Float.class) {
            return Float.parseFloat(source.toString());
        } else if (clazz == Short.class) {
            return Short.parseShort(source.toString());
        } else if (clazz == Long.class) {
            return Long.parseLong(source.toString());
        } else if (clazz == Boolean.class) {
            return Boolean.parseBoolean(source.toString());
        } else if (clazz == String.class) {
            return String.valueOf(source);
        } else if (clazz.isEnum()) {
            return EnumUtils.parseEnumObject(source.toString(), clazz);
        }

        return clazz.newInstance();
    }

    /**
     * Check is the field requires to omit
     */
    private static boolean isOmit(Field sourceField, List<Field> fields) {
        Assert.notNull(fields, "Fields must have elements");
        for (Field field : fields) {
            if (getFieldName(sourceField) == getFieldName(field)
                    && sourceField.getType() == field.getType()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Return @BeanProperty value if is not Null/Empty
     */
    private static String getFieldName(Field field) {
        if (field.isAnnotationPresent(BeanProperty.class)) {
            String val = field.getAnnotation(BeanProperty.class).value();
            return StringUtils.isEmpty(val) ? field.getName() : val;
        }
        return field.getName();
    }

    /**
     * Check if is basic data structure
     */
    private static boolean isBasicData(Field field) {
        Class<?> clazz = field.getType();
        if (isBasicClass(clazz) || clazz.isEnum() || clazz == String.class) {
            return true;
        }
        return false;
    }

    /**
     * Check if is List Data Structure
     */
    private static boolean isListField(Field field) {
        Class<?> clazz = field.getType();
        return List.class.isAssignableFrom(clazz);
    }

    /**
     * Check if is basic class
     */
    private static boolean isBasicClass(Class<?> clazz) {
        if (clazz == Integer.class || clazz == Character.class || clazz == Byte.class
                || clazz == Double.class || clazz == Float.class || clazz == Short.class
                || clazz == Long.class || clazz == Boolean.class || clazz.isPrimitive()) {
            return true;
        }
        return false;
    }

    /**
     * 指定字段拷贝field
     *
     * @param source     源
     * @param target     目标
     * @param copyParams 指定的字段
     */
    public static void copyPropertiesAssignParam(Object source, Object target, String... copyParams) {

        Assert.notNull(source, "Source must not be null");
        Assert.notNull(target, "Target must not be null");

        if (copyParams == null) {
            // 需要拷贝的字段为空
            return;
        }

        List<String> copyParamList = Arrays.asList(copyParams);

        // 获取需要编辑的Field
        Class<?> targetClass = target.getClass();
        Stream<PropertyDescriptor> targetPds =
                Arrays.stream(BeanUtils.getPropertyDescriptors(targetClass));
        List<PropertyDescriptor> actualTargetPds =
                targetPds.filter(t -> copyParamList.contains(t.getName())).collect(Collectors.toList());

        for (PropertyDescriptor targetPd : actualTargetPds) {
            Method writeMethod = targetPd.getWriteMethod();
            if (writeMethod != null) {
                PropertyDescriptor sourcePd =
                        BeanUtils.getPropertyDescriptor(source.getClass(), targetPd.getName());
                if (sourcePd != null) {
                    Method readMethod = sourcePd.getReadMethod();
                    if (readMethod != null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0],
                            readMethod.getReturnType())) {
                        try {
                            if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                                readMethod.setAccessible(true);
                            }
                            Object value = readMethod.invoke(source);
                            if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                writeMethod.setAccessible(true);
                            }
                            writeMethod.invoke(target, value);
                        } catch (Throwable ex) {
                            throw new FatalBeanException(
                                    "Could not copy property '" + targetPd.getName() + "' from source to target", ex);
                        }
                    }
                }
            }
        }
    }

    /**
     * 拷贝字段，忽略source中为null的字段
     *
     * @param source 源对象
     * @param target 目标对象
     */
    public static void copyIgnoreNullField(Object source, Object target) {
        try {
            deepCopyProcess(source, target);
        } catch (Exception e) {
            log.warn("深拷贝失败，暂无处理，source {}， target {}", source, target);
        }
    }

    /**
     * 拷贝字段，可指定忽略拷贝那些字段，以及是否忽略source中为null的值
     *
     * @param source           源对象
     * @param target           目标对象
     * @param ignoreNull       是否忽略source中为null的对象
     * @param enumStringCast   是否开启枚举值和字符串之间的互相转换
     * @param ignoreProperties 忽略的字段
     */
    public static void copyProperties(Object source, Object target, Boolean ignoreNull,
                                      Boolean enumStringCast, String... ignoreProperties) {

        Assert.notNull(source, "Source must not be null");
        Assert.notNull(target, "Target must not be null");

        Class<?> actualEditable = target.getClass();
        PropertyDescriptor[] targetPds = BeanUtils.getPropertyDescriptors(actualEditable);
        List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);

        for (PropertyDescriptor targetPd : targetPds) {
            Method writeMethod = targetPd.getWriteMethod();
            if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
                PropertyDescriptor sourcePd =
                        BeanUtils.getPropertyDescriptor(source.getClass(), targetPd.getName());
                if (sourcePd != null) {
                    Method readMethod = sourcePd.getReadMethod();
                    if (readMethod != null) {
                        try {
                            if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                                readMethod.setAccessible(true);
                            }
                            Object value = readMethod.invoke(source);
                            // 忽略为null的field
                            if (!ignoreNull || Objects.nonNull(value)) {
                                if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                    writeMethod.setAccessible(true);
                                }
                                Class<?> parameterType = writeMethod.getParameterTypes()[0];
                                if (ClassUtils.isAssignable(parameterType, readMethod.getReturnType())) {
                                    writeMethod.invoke(target, value);
                                } else if (enumStringCast != null && enumStringCast) {
                                    // 开启了拷贝时，枚举和String在赋值时的强制转换
                                    copyEnumString(target, writeMethod, parameterType, value);
                                }
                            }
                        } catch (Throwable ex) {
                            throw new FatalBeanException(
                                    "Could not copy property '" + targetPd.getName() + "' from source to target", ex);
                        }
                    }
                }
            }
        }
    }

    /**
     * 开启了拷贝时，枚举和String在赋值时的强制转换
     *
     * @param t             拷贝执行的对象
     * @param writeMethod   t的写方法
     * @param parameterType 写参数的类型
     * @param value         写参数的值
     * @param <T>           泛型
     * @throws InvocationTargetException 异常
     * @throws IllegalAccessException    异常
     */
    private static <T> void copyEnumString(T t, Method writeMethod, Class<?> parameterType,
                                           Object value) throws InvocationTargetException, IllegalAccessException {
        if (Objects.isNull(value)) {
            writeMethod.invoke(t, (T) null);
        } else {
            if (parameterType.isEnum()) {
                // 用这个工具的意义是，如果匹配失败会返回null，不会抛异常
                Object objectValue = EnumUtils.parseEnumObject(value.toString(), parameterType);
                writeMethod.invoke(t, objectValue);
            } else if (parameterType.isAssignableFrom(String.class)) {
                writeMethod.invoke(t, value.toString());
            }
        }
    }
}
